Turborepo でモノレポ構成のプロジェクトを爆速でビルドする
Turborepo Demo and Walkthrough (High-Performance Monorepos)という動画で紹介されていた内容を試してみました。
Turborepo とは
Turborepoは Vercel が開発した JavaScript/TypeScript のモノレポ環境に特化したビルドツールです。yarn, npm, pnpm を利用している環境で利用可能です。
workspaces を利用してモノレポ環境で CI を実行する際、不要なリソースにまでコンパイル・ビルド・テストが実行されてしまいプロジェクトがスケールするにつれ CI にかかる時間が膨大になってしまいます。Turborepo はこの問題を解決するためのツールになります。
本エントリでご紹介する Turborepo の主要機能は以下です:
- 変更に対して必要な部分のみビルドを行う
- レポジトリ全体の依存をグラフで出力する
- リモートキャッシュ機能で最新のビルドのキャッシュをクラウド環境(Vercel)へ保存しチームで利用できる(Beta)
Turborepo 自体は Go 言語 で作られています。
変更点に対して必要な部分のみビルドを行う
公式が用意しているサンプルプロジェクトを利用して Turborepo を試します。
サンプルには 2 つの Next.js アプリケーションと共通で利用する設定ファイルとコンポーネント集が含まれます。
npx create-turbo@latest # ./my-turborepoにサンプルプロジェクトが作成されます # - apps/web: Next.js with TypeScript # - apps/docs: Next.js with TypeScript # - packages/ui: Shared React component library # - packages/config: Shared configuration (ESLint) # - packages/tsconfig: Shared TypeScript `tsconfig.json`
サンプルプロジェクト構造
cd my-turborepo tree -I node_modules . ├── README.md ├── apps │ ├── docs │ │ ├── README.md │ │ ├── next-env.d.ts │ │ ├── next.config.js │ │ ├── package.json │ │ ├── pages │ │ │ └── index.tsx │ │ └── tsconfig.json │ └── web │ ├── README.md │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ │ └── index.tsx │ └── tsconfig.json ├── package.json ├── packages │ ├── config │ │ ├── eslint-preset.js │ │ └── package.json │ ├── tsconfig │ │ ├── README.md │ │ ├── base.json │ │ ├── nextjs.json │ │ ├── package.json │ │ └── react-library.json │ └── ui │ ├── Button.tsx │ ├── index.tsx │ ├── package.json │ └── tsconfig.json ├── turbo.json └── yarn.lock
ビルド設定
Turborepo の設定は Root にある package.json 内に記述されています。
workspaces
で設定しているapp/
、packages/
配下にあるプロジェクト(web, docs, config, ui, tsconfig)がスコープに含まれます。
package.json
{ "name": "turborepo-basic-shared", "version": "0.0.0", "private": true, "workspaces": ["apps/*", "packages/*"], "scripts": { "build": "turbo run build", "dev": "turbo run dev --parallel", "lint": "turbo run lint", "format": "prettier --write \"**/*.{ts,tsx,md}\"" }, "devDependencies": { "prettier": "^2.5.1", "turbo": "latest" }, "engines": { "npm": ">=7.0.0", "node": ">=14.0.0" }, "packageManager": "yarn@1.22.11" }
ビルドを実行してみる
このままyarn turbo run build
を実行してみます。初回なので app
, docs
の両方のビルドが走ります。
my-turborepo git:(tutorial-1) ✗ yarn turbo run build yarn run v1.22.11 $ /blogs/2022/Feb/Sandbox/turborepo/src/my-turborepo/node_modules/.bin/turbo run build • Packages in scope: config, docs, tsconfig, ui, web • Running build in 5 packages docs:build: cache miss, executing 3dd856e65f14080c web:build: cache miss, executing 03337e8e78ba011c web:build: $ next build docs:build: $ next build docs:build: Attention: Next.js now collects completely anonymous telemetry regarding usage. docs:build: This information is used to shape Next.js' roadmap and prioritize features. docs:build: You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: docs:build: https://nextjs.org/telemetry docs:build: web:build: Attention: Next.js now collects completely anonymous telemetry regarding usage. web:build: This information is used to shape Next.js' roadmap and prioritize features. web:build: You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: web:build: https://nextjs.org/telemetry web:build: docs:build: info - Checking validity of types... web:build: info - Checking validity of types... web:build: info - Creating an optimized production build... docs:build: info - Creating an optimized production build... web:build: info - Compiled successfully web:build: info - Collecting page data... docs:build: info - Compiled successfully docs:build: info - Collecting page data... docs:build: info - Generating static pages (0/3) web:build: info - Generating static pages (0/3) docs:build: info - Generating static pages (3/3) web:build: info - Generating static pages (3/3) docs:build: info - Finalizing page optimization... docs:build: docs:build: Page Size First Load JS docs:build: ┌ ○ / 305 B 71.3 kB docs:build: └ ○ /404 193 B 71.2 kB docs:build: + First Load JS shared by all 71 kB docs:build: ├ chunks/framework-71b7ac9748a53568.js 42 kB docs:build: ├ chunks/main-24e9726f06e44d56.js 26.9 kB docs:build: ├ chunks/pages/\_app-2ad88a182aea1df3.js 1.37 kB docs:build: └ chunks/webpack-45f9f9587e6c08e1.js 729 B docs:build: docs:build: ○ (Static) automatically rendered as static HTML (uses no initial props) docs:build: web:build: info - Finalizing page optimization... web:build: web:build: Page Size First Load JS web:build: ┌ ○ / 304 B 71.3 kB web:build: └ ○ /404 193 B 71.2 kB web:build: + First Load JS shared by all 71 kB web:build: ├ chunks/framework-71b7ac9748a53568.js 42 kB web:build: ├ chunks/main-24e9726f06e44d56.js 26.9 kB web:build: ├ chunks/pages/\_app-2ad88a182aea1df3.js 1.37 kB web:build: └ chunks/webpack-45f9f9587e6c08e1.js 729 B web:build: web:build: ○ (Static) automatically rendered as static HTML (uses no initial props) web:build: Tasks: 2 successful, 2 total Cached: 0 cached, 2 total Time: 7.996s ✨ Done in 8.69s.
次に同じコマンドを再度実行してみます。
yarn turbo run build yarn run v1.22.11 $ /blogs/2022/Feb/Sandbox/turborepo/src/my-turborepo/node_modules/.bin/turbo run build • Packages in scope: config, docs, tsconfig, ui, web • Running build in 5 packages docs:build: cache hit, replaying output 3dd856e65f14080c docs:build: $ next build docs:build: Attention: Next.js now collects completely anonymous telemetry regarding usage. docs:build: This information is used to shape Next.js' roadmap and prioritize features. docs:build: You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: docs:build: https://nextjs.org/telemetry docs:build: docs:build: info - Checking validity of types... docs:build: info - Creating an optimized production build... docs:build: info - Compiled successfully docs:build: info - Collecting page data... docs:build: info - Generating static pages (0/3) docs:build: info - Generating static pages (3/3) docs:build: info - Finalizing page optimization... docs:build: docs:build: Page Size First Load JS docs:build: ┌ ○ / 305 B 71.3 kB docs:build: └ ○ /404 193 B 71.2 kB docs:build: + First Load JS shared by all 71 kB docs:build: ├ chunks/framework-71b7ac9748a53568.js 42 kB docs:build: ├ chunks/main-24e9726f06e44d56.js 26.9 kB docs:build: ├ chunks/pages/_app-2ad88a182aea1df3.js 1.37 kB docs:build: └ chunks/webpack-45f9f9587e6c08e1.js 729 B docs:build: docs:build: ○ (Static) automatically rendered as static HTML (uses no initial props) docs:build: web:build: cache hit, replaying output 03337e8e78ba011c web:build: $ next build web:build: Attention: Next.js now collects completely anonymous telemetry regarding usage. web:build: This information is used to shape Next.js' roadmap and prioritize features. web:build: You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: web:build: https://nextjs.org/telemetry web:build: web:build: info - Checking validity of types... web:build: info - Creating an optimized production build... web:build: info - Compiled successfully web:build: info - Collecting page data... web:build: info - Generating static pages (0/3) web:build: info - Generating static pages (3/3) web:build: info - Finalizing page optimization... web:build: web:build: Page Size First Load JS web:build: ┌ ○ / 304 B 71.3 kB web:build: └ ○ /404 193 B 71.2 kB web:build: + First Load JS shared by all 71 kB web:build: ├ chunks/framework-71b7ac9748a53568.js 42 kB web:build: ├ chunks/main-24e9726f06e44d56.js 26.9 kB web:build: ├ chunks/pages/_app-2ad88a182aea1df3.js 1.37 kB web:build: └ chunks/webpack-45f9f9587e6c08e1.js 729 B web:build: web:build: ○ (Static) automatically rendered as static HTML (uses no initial props) web:build: Tasks: 2 successful, 2 total Cached: 2 cached, 2 total Time: 163ms >>> FULL TURBO ✨ Done in 0.38s.
前回のキャッシュが参照され、ビルドがスキップされました。
アプリケーションの片方に変更を加えてみる
apps/
配下のweb
に変更を加え再度ビルドコマンドを実行してみます。
import {Button} from "ui"; export default function Web() { return ( <div> <h1>Web APP</h1>/* h1の文字列をWEB -> WEB APPへ変更*/ <Button /> </div> ); }
結果:
✗ yarn turbo run build yarn run v1.22.11 $ /blogs/2022/Feb/Sandbox/turborepo/src/my-turborepo/node_modules/.bin/turbo run build • Packages in scope: config, docs, tsconfig, ui, web • Running build in 5 packages web:build: cache miss, executing 96f1e5e0ec51b434 docs:build: cache hit, replaying output 3dd856e65f14080c docs:build: $ next build docs:build: Attention: Next.js now collects completely anonymous telemetry regarding usage. docs:build: This information is used to shape Next.js' roadmap and prioritize features. docs:build: You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: docs:build: https://nextjs.org/telemetry docs:build: docs:build: info - Checking validity of types... docs:build: info - Creating an optimized production build... docs:build: info - Compiled successfully docs:build: info - Collecting page data... docs:build: info - Generating static pages (0/3) docs:build: info - Generating static pages (3/3) docs:build: info - Finalizing page optimization... docs:build: docs:build: Page Size First Load JS docs:build: ┌ ○ / 305 B 71.3 kB docs:build: └ ○ /404 193 B 71.2 kB docs:build: + First Load JS shared by all 71 kB docs:build: ├ chunks/framework-71b7ac9748a53568.js 42 kB docs:build: ├ chunks/main-24e9726f06e44d56.js 26.9 kB docs:build: ├ chunks/pages/_app-2ad88a182aea1df3.js 1.37 kB docs:build: └ chunks/webpack-45f9f9587e6c08e1.js 729 B docs:build: docs:build: ○ (Static) automatically rendered as static HTML (uses no initial props) docs:build: web:build: $ next build web:build: info - Checking validity of types... web:build: info - Creating an optimized production build... web:build: info - Compiled successfully web:build: info - Collecting page data... web:build: info - Generating static pages (0/3) web:build: info - Generating static pages (3/3) web:build: info - Finalizing page optimization... web:build: web:build: Page Size First Load JS web:build: ┌ ○ / 308 B 71.3 kB web:build: └ ○ /404 193 B 71.2 kB web:build: + First Load JS shared by all 71 kB web:build: ├ chunks/framework-71b7ac9748a53568.js 42 kB web:build: ├ chunks/main-24e9726f06e44d56.js 26.9 kB web:build: ├ chunks/pages/_app-2ad88a182aea1df3.js 1.37 kB web:build: └ chunks/webpack-45f9f9587e6c08e1.js 729 B web:build: web:build: ○ (Static) automatically rendered as static HTML (uses no initial props) web:build: Tasks: 2 successful, 2 total Cached: 1 cached, 2 total Time: 4.681s ✨ Done in 5.12s.
web
のみがビルドされ変更を加えていないdocs
のビルドはスキップされました。ビルド時間も全体の半分に短縮されました。
レポジトリ全体の依存をグラフで出力する
--graph
オプションをつけることで依存関係の図の出力ができます。
イメージの生成にはgraphvizがインストールしてある必要があります。
# graphvizをダウンロード brew install graphviz # 依存関係のグラフを生成 yarn turbo run build --graph ✔ Generated task graph in graph-1645622506364370000.jpg ✨ Done in 3.66s.
graph-1645622506364370000.jpg
app
とdocs
という2つの Next.js アプリケーションはどちらもui/Button.tsx
のコンポーネントを参照しているのが図で確認できます。
リモートキャッシュ機能で最新のビルドのキャッシュをクラウド環境(Vercel)へ保存しチームで利用できる
ビルドアーティファクトを Vercel に保存しビルドキャッシュをチームで共有することができるリモートキャッシング機能(Beta)を利用することができます。
login コマンドで Vercel アカウントにログインします。
✗ npx turbo login >>> Opening browser to https://vercel.com Waiting for your authorization... >>> Success! Turborepo CLI authorized for xxxxxx@classmethod.jp
次にリモートキャッシュを利用するための設定を行います。
npx turbo link >>> Remote Caching (beta) Remote Caching shares your cached Turborepo task outputs and logs across all your team’s Vercel projects. It also can share outputs with other services that enable Remote Caching, like CI/CD systems. This results in faster build times and deployments for your team. For more info, see https://turborepo.org/docs/features/remote-caching ? Would you like to enable Remote Caching for "~/blogs/2022/Feb/Sandbox/turborepo/src/my-turborepo"? Yes ? Which Vercel scope (and Remote Cache) do you want associate with this Turborepo? your_vercel_scope >>> Success! Turborepo CLI authorized for xxxxx To disable Remote Caching, run `npx turbo unlink`
上の設定でリモートキャッシング機能が有効化されたのでローカルにキャッシュされているビルド履歴(node_modules/.cache/turbo)を削除して再度ビルドコマンドを実行してみます。
# ローカル環境のキャッシュを削除 rm -rf ./node_modules/.cache/turbo # ビルドコマンドを実行 yarn turbo run build yarn run v1.22.11 $ /blogs/2022/Feb/Sandbox/turborepo/src/my-turborepo/node_modules/.bin/turbo run build • Packages in scope: config, docs, tsconfig, ui, web • Remote computation caching enabled (experimental) • Running build in 5 packages docs:build: cache hit, replaying output 3dd856e65f14080c docs:build: $ next build docs:build: info - Checking validity of types... docs:build: info - Creating an optimized production build... docs:build: info - Compiled successfully docs:build: info - Collecting page data... docs:build: info - Generating static pages (0/3) docs:build: info - Generating static pages (3/3) docs:build: info - Finalizing page optimization... docs:build: docs:build: Page Size First Load JS docs:build: ┌ ○ / 305 B 71.3 kB docs:build: └ ○ /404 193 B 71.2 kB docs:build: + First Load JS shared by all 71 kB docs:build: ├ chunks/framework-71b7ac9748a53568.js 42 kB docs:build: ├ chunks/main-24e9726f06e44d56.js 26.9 kB docs:build: ├ chunks/pages/_app-2ad88a182aea1df3.js 1.37 kB docs:build: └ chunks/webpack-45f9f9587e6c08e1.js 729 B docs:build: docs:build: ○ (Static) automatically rendered as static HTML (uses no initial props) docs:build: web:build: cache hit, replaying output 96f1e5e0ec51b434 web:build: $ next build web:build: info - Checking validity of types... web:build: info - Creating an optimized production build... web:build: info - Compiled successfully web:build: info - Collecting page data... web:build: info - Generating static pages (0/3) web:build: info - Generating static pages (3/3) web:build: info - Finalizing page optimization... web:build: web:build: Page Size First Load JS web:build: ┌ ○ / 308 B 71.3 kB web:build: └ ○ /404 193 B 71.2 kB web:build: + First Load JS shared by all 71 kB web:build: ├ chunks/framework-71b7ac9748a53568.js 42 kB web:build: ├ chunks/main-24e9726f06e44d56.js 26.9 kB web:build: ├ chunks/pages/_app-2ad88a182aea1df3.js 1.37 kB web:build: └ chunks/webpack-45f9f9587e6c08e1.js 729 B web:build: web:build: ○ (Static) automatically rendered as static HTML (uses no initial props) web:build: Tasks: 2 successful, 2 total Cached: 2 cached, 2 total Time: 2.215s >>> FULL TURBO
Remote computation caching enabled (experimental)
と表示されリモート環境のキャッシュを参照しているのが確認できました。ビルドの内容は変わらないのでそのままキャッシュの内容が反映されビルド自体はスキップされました。
リモートキャッシング機能自体は無料で利用できるのでチーム開発にすでに Vercel を利用している場合は嬉しいですね。